import wx
import datetime
import threading

from login import LdapLogin
from models import MyTextBox, JobWithResults


class JobViewer(wx.Frame):
    def __init__(self, logger, parent, title, queue, jobHandler, jobsToSendList, configData):
        super(JobViewer, self).__init__(None, title=title, style=wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP)
        self._queue = queue
        self._jobsToSendList = jobsToSendList
        self._jobList = []
        self._configData = configData
        self._TextFileContent = None
        self._currentSelection = None
        self._queueLength = 0
        self._parent = parent
        self._runningC = wx.Colour(200, 255, 255)
        self._waitingC = wx.Colour(255, 200, 255)
        self._jobHandler = jobHandler
        self._logger = logger
        self._actualRow = None
        self._headerLogInfo = "[" + "JOB_VIEWER".rjust(19) + "]\t"
        self._login = None

        self.initGUI()
        self.SetSize((620, 170))
        self.Centre()
        self.Show()

    def initGUI(self):
        try:
            self.Bind(wx.EVT_CLOSE, self.onClose)

            self._listCtrl = wx.ListCtrl(self, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES | wx.EXPAND)
            self._listCtrl.InsertColumn(0, 'QueueID', wx.LIST_FORMAT_CENTRE, width=60)
            self._listCtrl.InsertColumn(1, 'Project', wx.LIST_FORMAT_CENTRE, width=120)
            self._listCtrl.InsertColumn(2, 'Branch', wx.LIST_FORMAT_CENTRE, width=60)
            self._listCtrl.InsertColumn(3, 'Test', wx.LIST_FORMAT_CENTRE, width=60)
            self._listCtrl.InsertColumn(4, 'BuildID', wx.LIST_FORMAT_CENTRE, width=60)
            self._listCtrl.InsertColumn(5, 'Status', wx.LIST_FORMAT_CENTRE, width=120)
            self._listCtrl.InsertColumn(6, 'Queue time [d h:m]', wx.LIST_FORMAT_CENTRE, width=120)
            self._listCtrl.Bind(wx.EVT_LEFT_DOWN, self.onLeftDown)
            self._listCtrl.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown)

            sizer = wx.BoxSizer(wx.VERTICAL)
            sizer.Add(self._listCtrl, 1, wx.EXPAND | wx.ALL)
            self.SetSizer(sizer)
            self._logger.info(self._headerLogInfo + "Viewer started")
            self.showJobList()
        except Exception, e:
            self._logger.exception(self._headerLogInfo + "Job Viewer InitGUI Crash: {}".format(str(e)))
            self._parent.destroyViewer()

    def showJobList(self):
        self._logger.debug(self._headerLogInfo + "Updating jobs view list")
        try:
            self.Freeze()
            self._logger.debug(self._headerLogInfo + "Freezeing actual view")
            self._jobList = self._queue.getJobsList()
            self._queueLength = len(self._jobList)
            self._logger.debug(self._headerLogInfo + "Queue list, length = {}".format(self._queueLength))
            self._listCtrl.DeleteAllItems()
            self._logger.debug(self._headerLogInfo + "Clearing actual jobs list")
            for i in range(len(self._jobList)):
                self._listCtrl.InsertItem(i, str(i + 1))
                self._listCtrl.SetItem(i, 1, self._jobList[i]._project)
                self._listCtrl.SetItem(i, 2, self._jobList[i]._branch)
                self._listCtrl.SetItem(i, 3, self._jobList[i]._testType)
                self._listCtrl.SetItem(i, 4, self._jobList[i]._buildId)
                self._listCtrl.SetItem(i, 5, self._jobList[i]._status)
                queueTime = datetime.datetime.now() - self._jobList[i]._startTime
                formattedTime = self.getTime(queueTime.total_seconds())
                self._listCtrl.SetItem(i, 6, formattedTime)
                self._listCtrl.EnsureVisible(i)
                self._logger.debug(self._headerLogInfo + "Adding job item [Item ID = {}]: {}/{}/{}/{}/{}/{}"
                                   .format(i + 1,
                                           self._jobList[i]._project,
                                           self._jobList[i]._branch,
                                           self._jobList[i]._testType,
                                           self._jobList[i]._buildId,
                                           self._jobList[i]._status,
                                           formattedTime))
                if self._jobList[i]._status == 'running':
                    self._listCtrl.SetItemBackgroundColour(i, self._runningC)
                else:
                    self._listCtrl.SetItemBackgroundColour(i, self._waitingC)
            list_total = self._listCtrl.GetItemCount()
            list_top = self._listCtrl.GetTopItem()
            list_pp = self._listCtrl.GetCountPerPage()
            list_bottom = min(list_top + list_pp, list_total - 1)
            self._listCtrl.EnsureVisible((list_bottom - 1))
            self.Update()
            self.Thaw()
            self._logger.debug(self._headerLogInfo + "Releasing frozen view")
            self._currentSelection = [0] * self._queueLength
            self._queue.updateView(False)
        except Exception, e:
            self._logger.exception(self._headerLogInfo + "Job Viewer showJobList Crash: {}".format(str(e)))
            self._parent.destroyViewer()

    def getTime(self, queueTime):
        days, remainder = divmod(queueTime, 3600 * 24)
        hours, remainder = divmod(remainder, 3600)
        minutes, remainder = divmod(remainder, 60)
        return str(int(days)) + " " + str(int(hours)).zfill(2) + ":" + str(int(minutes)).zfill(2)

    def onRightDown(self, event):
        x, y = event.GetPosition()
        row, flags = self._listCtrl.HitTest((x, y))
        if flags != wx.LIST_HITTEST_NOWHERE:
            self._logger.debug(self._headerLogInfo + "Righ mouse button has been clicked. Selecting item: [Item ID={}]"
                               .format(row + 1))
            if self._currentSelection[row] == 0:
                self.deselectAllRows()
                self._listCtrl.Select(row)
                self._currentSelection[row] = 1
            menu = self.createListItemMenu(row)
            self.PopupMenu(menu)
            menu.Destroy()
        else:
            self._logger.debug(self._headerLogInfo + "Righ mouse button clicked outside of list. Deselecting all items")
            self.deselectAllRows()

    def createListItemMenu(self, row):
        self._logger.debug(self._headerLogInfo + "Creating popup menu for item: [Item ID={}]".format(row + 1))
        menu = wx.Menu()
        if ((row == 0 and self._jobList[0]._status == "waiting") or row > 0):
            self.createMenuItem(menu, "Set Highest Priority", self.onListItemMenuSetHighestPriority, row)
            self.createMenuItem(menu, "Set Lowest Priority", self.onListItemMenuSetLowestPriority, row)
            menu.AppendSeparator()
        self.createMenuItem(menu, "Remove From Queue", self.onListItemMenuRemoveFromQueue, row)
        menu.AppendSeparator()
        self.createMenuItem(menu, "Cancel", self.onListItemMenuCancel, row)
        self._logger.debug(self._headerLogInfo + "Popup menu created")
        return menu

    def onListItemMenuSetHighestPriority(self, event, row):
        self._actualRow = row
        self._logger.debug(self._headerLogInfo + "Menu 'Set Highest Priority' has been chosen for: [Item ID = {}]"
                           .format(row))
        if self._login is None:
            self._login = LdapLogin(self.setHighestPriority, self._configData['ldapServer'],
                                    self._configData['ldapPort'], self._logger, self._headerLogInfo)

    def setHighestPriority(self, returnCode, username, exception):
        job = "{}/{}/{}/{}".format(self._jobList[self._actualRow]._project, self._jobList[self._actualRow]._branch,
                                   self._jobList[self._actualRow]._testType, self._jobList[self._actualRow]._buildId)
        if returnCode:
            prio = 0
            if self._jobList[0]._status == "running":
                self._queue.setPriority(self._actualRow, 1)
                prio = 2
            else:
                self._queue.setPriority(self._actualRow, 0)
                prio = 1
            self._logger.info(self._headerLogInfo + "User {} set highest ({}) priority for job: {}"
                              .format(username, str(prio), job))
        else:
            self._logger.info(self._headerLogInfo + "Error while trying to set highest priority for job: {}, user: ({})"
                              .format(job, username) + ". Exception: {}".format(exception))
        if self._login is not None:
            self._login.Destroy()
            self._login = None

    def onListItemMenuSetLowestPriority(self, event, row):
        self._actualRow = row
        self._logger.debug(self._headerLogInfo + "Menu 'Set Lowest Priority' has been chosen for: [Item ID = {}]"
                           .format(row))
        if self._login is None:
            self._login = LdapLogin(self.setLowestPriority, self._configData['ldapServer'],
                                    self._configData['ldapPort'], self._logger, self._headerLogInfo)

    def setLowestPriority(self, returnCode, username, exception):
        job = "{}/{}/{}/{}".format(self._jobList[self._actualRow]._project, self._jobList[self._actualRow]._branch,
                                   self._jobList[self._actualRow]._testType, self._jobList[self._actualRow]._buildId)
        if returnCode:
            self._queue.setPriority(self._actualRow, prio=len(self._jobList) - 1)
            self._logger.info(self._headerLogInfo + "User {} set lowest ({}) priority for job: {}"
                              .format(job, str(len(self._jobList)), job))
        else:
            self._logger.info(self._headerLogInfo + "Error while trying to set lowest ({}".format(len(self._jobList)) +
                              + ") priority for job: {} (user: {}). Exception: {}".format(job, username, exception))
        if self._login is not None:
            self._login.Destroy()
            self._login = None

    def onListItemMenuCancel(self, event, row):
        self._logger.debug(self._headerLogInfo + "Menu 'Cancel' has been chosen")
        pass

    def onListItemMenuRemoveFromQueue(self, event, row):
        self._actualRow = row
        self._logger.debug(self._headerLogInfo + "Menu 'Remove From Queue' has been chosen for: [Item ID = {}]"
                           .format(row))
        if self._login is None:
            self._login = LdapLogin(self.removeFromQUeue, self._configData['ldapServer'],
                                    self._configData['ldapPort'], self._logger, self._headerLogInfo)

    def removeFromQUeue(self, returnCode, username, exception):
        job = "{}/{}/{}/{}".format(self._jobList[self._actualRow]._project, self._jobList[self._actualRow]._branch,
                                   self._jobList[self._actualRow]._testType, self._jobList[self._actualRow]._buildId)
        if returnCode:
            self._logger.info(self._headerLogInfo + "Removing job from queue: {}".format(job))
            if (self._actualRow > 0 or (self._actualRow == 0 and self._jobList[0]._status == "waiting")):
                jobWithResults = JobWithResults(self._jobList[self._actualRow],
                                                -1, "", "Tests removed from queue by user")
                self._jobsToSendList.put(jobWithResults)
                self._queue.rem(self._actualRow)
            else:
                if self._jobHandler.getRunningJob() is not None:
                    timer = threading.Timer(0, self._jobHandler.killRunningJob,
                                            ["Wait for safe Job removal! Might take a while.", True])
                    timer.start()
            self._logger.info(self._headerLogInfo + "User {} removed job {} from queue".format(username, job))
        else:
            self._logger.warning(self._headerLogInfo + "Error while trying to remove job {} from queue "
                                 .format(job) + "(user: {}). Exception: {}".format(username, exception))
        if self._login is not None:
            self._login.Destroy()
            self._login = None

    def createMenuItem(self, menu, label, func, row):
        self._logger.debug(self._headerLogInfo + "Creating menu item: {}, for popup menu".format(label))
        menuItem = wx.MenuItem(menu, -1, label)
        menu.Bind(wx.EVT_MENU, lambda event, args=row: func(event, row), id=menuItem.GetId())
        menu.Append(menuItem)
        self._logger.debug(self._headerLogInfo + "Menu item: {} added for popup menu".format(label))
        return menuItem

    def onLeftDown(self, event):
        x, y = event.GetPosition()
        row, flags = self._listCtrl.HitTest((x, y))
        if flags != wx.LIST_HITTEST_NOWHERE:
            self._logger.debug(self._headerLogInfo + "Left mouse button clicked")
            if self._currentSelection[row] == 1:
                self._logger.debug(self._headerLogInfo + "Deselecting item: [Item ID={}]".format(row + 1))
                self._listCtrl.Select(row, 0)
                self._currentSelection[row] = 0
            else:
                self._logger.debug(self._headerLogInfo + "Selecting item: [Item ID={}]".format(row + 1))
                self.deselectAllRows()
                self._listCtrl.Select(row)
                self._currentSelection[row] = 1
        else:
            self._logger.debug(self._headerLogInfo + "Left mouse button clicked outside of list. Deselecting all items")
            self.deselectAllRows()

    def deselectAllRows(self):
        for i in range(self._queueLength):
            self._listCtrl.Select(i, 0)
        self._currentSelection = [0] * self._queueLength

    def onClose(self, event):
        self._logger.info(self._headerLogInfo + "Closing viewer")
        self._parent.destroyViewer()
